Imports System.Runtime.InteropServices


Public Module Objects
	Class PyObject
		Implements IEnumerable
		Public ReadOnly p_self As IntPtr
		Public auto_decref As Boolean = False

		Sub New(ByVal self As IntPtr, Optional ByVal auto_incref As Boolean = True, _
		Optional ByVal auto_decref As Boolean = True)
			RaisePendingException()
			If self.Equals(IntPtr.Zero) Then
				Throw New ArgumentException("self is NULL")
			End If
			p_self = self
			Me.auto_decref = auto_decref
			If auto_incref Then CAPI.Py_IncRef(self)
		End Sub

		Protected Overrides Sub Finalize()
			If Me.auto_decref Then CAPI.Py_DecRef(p_self)
		End Sub

		Shared Function Create(ByVal value As Boolean) As PyObject
			Return New PyObject(CAPI.PyBool_FromLong(CInt(value)), False)
		End Function

		Shared Function Create(ByVal value As Integer) As PyObject
			Return New PyObject(CAPI.PyInt_FromLong(value), False)
		End Function

		Shared Function Create(ByVal value As Int64) As PyObject
			Return New PyObject(CAPI.PyLong_FromLongLong(value), False)
		End Function

		Shared Function Create(ByVal value As UInt64) As PyObject
			Return New PyObject(CAPI.PyLong_FromUnsignedLongLong(value), False)
		End Function

		Shared Function Create(ByVal value As Double) As PyObject
			Return New PyObject(CAPI.PyFloat_FromDouble(value), False)
		End Function

		Shared Function Create(ByVal value As String) As PyObject
			Return New PyObject(CAPI.PyString_FromString(value), False)
		End Function

		Shared Function Create(ByVal value As PyObject) As PyObject
			Return value
		End Function

		Shared Function Create(ByVal value As IntPtr) As PyObject
			Return New PyObject(value)
		End Function

		Shared Function Create(ByVal value As Object) As PyObject
			If TypeOf value Is Boolean Then
				Return Create(CBool(value))
			ElseIf TypeOf value Is Integer Then
				Return Create(CInt(value))
			ElseIf TypeOf value Is Int64 Then
				Return Create(CLng(value))
			ElseIf TypeOf value Is UInt64 Then
				Return Create(CType(value, UInt64))
			ElseIf TypeOf value Is Double Then
				Return Create(CDbl(value))
			ElseIf TypeOf value Is Single Then
				Return Create(CDbl(value))
			ElseIf TypeOf value Is String Then
				Return Create(CStr(value))
			ElseIf TypeOf value Is PyObject Then
				Return Create(CType(value, PyObject))
			ElseIf TypeOf value Is IntPtr Then
				Return Create(CType(value, IntPtr))
			ElseIf TypeOf value Is Array Then
				Return New PyTuple(CType(value, Array))
			ElseIf TypeOf value Is Collections.Hashtable Then
				Return New PyDict(CType(value, Collections.Hashtable))
			Else
				Throw New ArgumentException(String.Format("cannot create a PyObject for {0}", value.GetType().ToString()))
			End If
		End Function

		Function Simplify() As Object
			If TypeCheck(Builtins.bool) Then
				Return CBool(CAPI.PyInt_AsLong(p_self))
			ElseIf TypeCheck(Builtins.int) Then
				Return CAPI.PyInt_AsLong(p_self)
			ElseIf TypeCheck(Builtins.float) Then
				Return CAPI.PyFloat_AsDouble(p_self)
			ElseIf TypeCheck(Builtins.str) Then
				Return CAPI.PyString_AsString(p_self)
			Else
				Return Me
			End If
		End Function

		Function [Call](ByVal args As PyObject) As PyObject
			Return New PyObject(CAPI.PyObject_CallObject(p_self, args.p_self))
		End Function

		Function [Call](ByVal args As PyObject, ByVal kwargs As PyObject) As PyObject
			Return New PyObject(CAPI.PyObject_Call(p_self, args.p_self, kwargs.p_self))
		End Function

		Function [Call](ByVal ParamArray args() As Object) As PyObject
			Dim tuple As New PyTuple(args)
			Return New PyObject(CAPI.PyObject_CallObject(p_self, tuple.p_self))
		End Function

		Default Property Indexer(ByVal key As PyObject) As Object
			Get
				Return New PyObject(CAPI.PyObject_GetItem(p_self, key.p_self)).Simplify()
			End Get
			Set(ByVal value As Object)
				CAPI.PyObject_SetItem(p_self, key.p_self, Create(value).p_self)
			End Set
		End Property

		Default Property Indexer(ByVal key As Object) As Object
			Get
				Return New PyObject(CAPI.PyObject_GetItem(p_self, Create(key).p_self)).Simplify()
			End Get
			Set(ByVal value As Object)
				CAPI.PyObject_SetItem(p_self, Create(key).p_self, Create(value).p_self)
			End Set
		End Property

		Sub DelAttr(ByVal name As String)
			CAPI.PyObject_DelAttrString(p_self, name)
		End Sub

		Function HasAttr(ByVal name As String) As Boolean
			Return CBool(CAPI.PyObject_HasAttrString(p_self, name))
		End Function

		Overridable Function GetAttr(ByVal name As String) As PyObject
			Return New PyObject(CAPI.PyObject_GetAttrString(p_self, name), False)
		End Function

		Overridable Sub SetAttr(ByVal name As String, ByVal value As Object)
			CAPI.PyObject_SetAttrString(p_self, name, Create(value).p_self)
		End Sub

		Overridable Sub SetAttr(ByVal name As String, ByVal value As Integer)
			CAPI.PyObject_SetAttrString(p_self, name, Create(value).p_self)
		End Sub

		Overridable Sub SetAttr(ByVal name As String, ByVal value As String)
			CAPI.PyObject_SetAttrString(p_self, name, Create(value).p_self)
		End Sub

		Overridable Sub SetAttr(ByVal name As String, ByVal value As PyObject)
			CAPI.PyObject_SetAttrString(p_self, name, value.p_self)
		End Sub

		Overridable Function Len() As Integer
			Return CAPI.PyObject_Size(p_self)
		End Function

		Function Repr() As String
			Return CAPI.PyString_AsString(CAPI.PyObject_Repr(p_self))
		End Function

		Function Str() As String
			Return CAPI.PyString_AsString(CAPI.PyObject_Str(p_self))
		End Function

		Function [Unicode]() As String
			Return CAPI.PyUnicode_AsUnicod(CAPI.PyObject_Unicode(p_self))
		End Function

		Function Dir() As String()
			Return CType(New PyList(CAPI.PyObject_Dir(p_self), False).Copy(), String())
		End Function

		Function IsTrue() As Boolean
			Return CBool(CAPI.PyObject_IsTrue(p_self))
		End Function

		Function Type() As PyObject
			Return New PyObject(CAPI.PyObject_Type(p_self))
		End Function

		Function TypeCheck(ByVal type As PyObject) As Boolean
			Return CBool(CAPI.PyObject_TypeCheck(p_self, type.p_self))
		End Function

		Function IsInstance(ByVal type As PyObject) As Boolean
			Return CBool(CAPI.PyObject_IsInstance(p_self, type.p_self))
		End Function

		Function Iter() As PyIterator
			Return New PyIterator(CAPI.PyObject_GetIter(p_self), False)
		End Function

		Function Compare(ByVal other As Object, ByVal operation As CAPI.CompareOperator) As Boolean
			Return Compare(Create(other), operation)
		End Function

		Function Compare(ByVal other As PyObject) As Integer
			Dim res As Integer
			res = PyObject_Compare(p_self, other.p_self)
			RaisePendingException()
			Return res
		End Function

		Function RichCompare(ByVal other As Object, ByVal operation As CAPI.CompareOperator) As Boolean
			Return RichCompare(Create(other), operation)
		End Function

		Function RichCompare(ByVal other As PyObject, ByVal operation As CAPI.CompareOperator) As Boolean
			Dim res As Integer
			res = PyObject_RichCompareBool(p_self, other.p_self, operation)
			If res = 1 Then
				Return True
			ElseIf res = 0 Then
				Return False
			Else
				RaisePendingException()
			End If
		End Function

		Overrides Function ToString() As String
			Return Repr()
		End Function

		Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
			Return Iter()
		End Function
	End Class

	Class PyIterator
		Inherits PyObject
		Implements IEnumerator
		Private _current As PyObject
		Private _next As PyObject

		Sub New(ByVal self As IntPtr, Optional ByVal auto_incref As Boolean = True, _
		Optional ByVal auto_decref As Boolean = True)
			MyBase.New(self, auto_incref, auto_decref)
			_current = Nothing
			_next = GetAttr("next")
		End Sub

		Public ReadOnly Property Current() As Object Implements System.Collections.IEnumerator.Current
			Get
				Return _current.Simplify()
			End Get
		End Property

		Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
			Try
				_current = _next.Call()
				Return True
			Catch ex As PythonException When ex.Matches(Builtins.StopIteration)
				Return False
			End Try
		End Function

		Public Sub Reset() Implements System.Collections.IEnumerator.Reset
			Throw New NotSupportedException("python iterators cannot be reset")
		End Sub
	End Class

	Class PyTuple
		Inherits PyObject

		Sub New(ByVal ParamArray args() As Object)
			MyBase.New(CAPI.PyTuple_New(args.Length), False)
			For i As Integer = 0 To UBound(args)
				CAPI.PyTuple_SetItem(p_self, i, Create(args(i)).p_self)
			Next
		End Sub

		Sub New(ByVal self As IntPtr, Optional ByVal auto_incref As Boolean = True, _
		Optional ByVal auto_decref As Boolean = True)
			MyBase.New(self, auto_incref, auto_decref)
		End Sub

		Overrides Function Len() As Integer
			Return CAPI.PyList_Size(p_self)
		End Function

		Default Overloads ReadOnly Property Indexer(ByVal key As Integer) As Object
			Get
				Return New PyObject(CAPI.PyTuple_GetItem(p_self, key)).Simplify()
			End Get
		End Property

		Function Copy() As Object()
			Dim arr(Len() - 1) As Object
			For i As Integer = 0 To UBound(arr)
				arr(i) = Me(i)
			Next
		End Function

		Shadows Function Simplify() As Object()
			Return Copy()
		End Function
	End Class

	Class PyList
		Inherits PyObject

		Sub New(ByVal count As Integer)
			MyBase.New(CAPI.PyList_New(count), False)
		End Sub

		Sub New(ByVal ParamArray args() As Object)
			Me.New(args.Length)
			For i As Integer = 0 To UBound(args)
				Me(i) = args(i)
			Next
		End Sub

		Sub New(ByVal self As IntPtr, Optional ByVal auto_incref As Boolean = True, _
		Optional ByVal auto_decref As Boolean = True)
			MyBase.New(self, auto_incref, auto_decref)
		End Sub

		Overrides Function Len() As Integer
			Return CAPI.PyList_Size(p_self)
		End Function

		Default Overloads Property Indexer(ByVal key As Integer) As Object
			Get
				Return New PyObject(CAPI.PyList_GetItem(p_self, key)).Simplify()
			End Get
			Set(ByVal value As Object)
				CAPI.PyList_SetItem(p_self, key, Create(value).p_self)
			End Set
		End Property

		Function Copy() As Object()
			Dim arr(Len() - 1) As Object
			For i As Integer = 0 To UBound(arr)
				arr(i) = Me(i)
			Next
		End Function

		Shadows Function Simplify() As Object()
			Return Copy()
		End Function
	End Class

	Class PyDict
		Inherits PyObject

		Sub New()
			MyBase.New(CAPI.PyDict_New(), False)
		End Sub

		Sub New(ByVal table As Collections.Hashtable)
			Me.New()
			For Each item As Collections.DictionaryEntry In table
				Me(item.Key) = item.Value
			Next
		End Sub

		Sub New(ByVal self As IntPtr, Optional ByVal auto_incref As Boolean = True, _
		Optional ByVal auto_decref As Boolean = True)
			MyBase.New(self, auto_incref, auto_decref)
		End Sub

		Overrides Function Len() As Integer
			Return CAPI.PyDict_Size(p_self)
		End Function

		Sub Clear()
			CAPI.PyDict_Clear(p_self)
		End Sub

		Function Contains(ByVal key As Object) As Boolean
			CAPI.PyDict_Contains(p_self, Create(key).p_self)
		End Function

		Default Overloads Property Indexer(ByVal key As String) As Object
			Get
				' GetItem sucks, we have to set the exception for it... gha!
				Dim p As IntPtr = CAPI.PyDict_GetItemString(p_self, key)
				If p.Equals(IntPtr.Zero) Then
					CAPI.PyErr_SetString(Builtins.KeyError.p_self, key)
					RaisePendingException()
				Else
					Return New PyObject(p).Simplify()
				End If
			End Get
			Set(ByVal value As Object)
				CAPI.PyDict_SetItemString(p_self, key, Create(value).p_self)
			End Set
		End Property

		Function [Get](ByVal key As String, Optional ByVal [default] As PyObject = Nothing) As PyObject
			Dim p As IntPtr
			p = CAPI.PyDict_GetItemString(p_self, key)
			If p.Equals(IntPtr.Zero) Then
				If [default] Is Nothing Then
					Return None
				Else
					Return [default]
				End If
			End If
			Return New PyObject(p)
		End Function

		Function [Get](ByVal key As PyObject, Optional ByVal [default] As PyObject = Nothing) As PyObject
			Dim p As IntPtr
			p = CAPI.PyDict_GetItem(p_self, key.p_self)
			If p.Equals(IntPtr.Zero) Then
				If [default] Is Nothing Then
					Return None
				Else
					Return [default]
				End If
			End If
			Return New PyObject(p)
		End Function

		Function Copy() As Hashtable
			Dim length As Integer = Len()
			Dim table As New Collections.Hashtable(length)
			Dim items As PyObject, k, v As Object
			items = New PyList(CAPI.PyDict_Items(p_self), False)
			For i As Integer = 0 To length - 1
				v = CType(CType(items(i), PyObject)(0), PyObject).Simplify()
				k = CType(CType(items(i), PyObject)(1), PyObject).Simplify()
				table(k) = v
			Next
			Return table
		End Function

		Shadows Function Simplify() As Hashtable
			Return Copy()
		End Function
	End Class

	Class PyModuleDict
		Inherits PyDict

		Sub New()
			MyBase.New()
		End Sub

		Sub New(ByVal table As Collections.Hashtable)
			MyBase.New(table)
		End Sub

		Sub New(ByVal self As IntPtr, Optional ByVal auto_incref As Boolean = True, _
		Optional ByVal auto_decref As Boolean = True)
			MyBase.New(self, auto_incref, auto_decref)
		End Sub

		Overrides Function GetAttr(ByVal name As String) As PyObject
			Return Me(name)
		End Function

		Overloads Overrides Sub SetAttr(ByVal name As String, ByVal value As PyObject)
			Me(name) = value
		End Sub

		Default Shadows Property Indexer(ByVal key As String) As PyObject
			Get
				' GetItem sucks, we have to set the exception for it... gha!
				Dim p As IntPtr = CAPI.PyDict_GetItemString(p_self, key)
				If p.Equals(IntPtr.Zero) Then
					CAPI.PyErr_SetString(Builtins.KeyError.p_self, key)
					RaisePendingException()
				Else
					Return New PyObject(p)
				End If
			End Get
			Set(ByVal value As PyObject)
				CAPI.PyDict_SetItemString(p_self, key, value.p_self)
			End Set
		End Property
	End Class

End Module
